<?php  if (!defined('BASEPATH')) exit('No direct script access allowed');
// +----------------------------------------------------------------------+
// | Treacle PHP Framework Version 0.10                                   |
// +----------------------------------------------------------------------+
// | Copyright (c) 2007 Treacle.cn All rights reserved.					  |
// +----------------------------------------------------------------------+
// | Licensed under the Apache License, Version 2.0 (the 'License');      |
// | you may not use this file except in compliance with the License.     |
// | You may obtain a copy of the License at                              |
// | http://www.apache.org/licenses/LICENSE-2.0                           |
// | Unless required by applicable law or agreed to in writing, software  |
// | distributed under the License is distributed on an 'AS IS' BASIS,    |
// | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or      |
// | implied. See the License for the specific language governing         |
// | permissions and limitations under the License.                       |
// +----------------------------------------------------------------------+
// | Author: BoPo <ibopo@126.com>										  |
// +----------------------------------------------------------------------+
/**
 * TErrorHandler Class file
 *
 * @author BoPo <ibopo@126.com>
 * @link http://www.treacle.cn/
 * @copyright Copyright &copy; 2007 Treacle
 * @license http://www.treacle.cn/license/
 * @version $Id$
 * @package exception
 */
/**
 * 应用异常处理类,发生异常时触发
 *
 * @package	exception
 * @author BoPo <ibopo@126.com>
 * @since 0.10
 * @version	$Id $
 */
class TErrorHandler
{
	/**
	 * 默认语言
	 */
	private $lang = 'zh';
	/**
	 * 错误模板文件名
	 */
	const ERROR_FILE_NAME = 'error';
	/**
	 * 异常模板文件名
	 */
	const EXCEPTION_FILE_NAME = 'exception';
	/**
	 * 出现异常时显示源码的行数
	 */
	const SOURCE_LINES = 12;
	/**
	 * 错误模板目录
	 */
	private $_templatePath = null;
	/**
	 * 构造函数
	 */
	public function __construct()
	{
		$this->lang 			= (Configure::item('language')!=false)?Configure::item('language'):$this->lang;
		$this->_templatePath 	= BASEPATH."/languages/{$this->lang}/exception";
	}
	/**
	 * 获取错误模板目录
	 * @return string 存放错误模板的目录.
	 */
	public function getErrorTemplatePath()
	{
		return $this->_templatePath;
	}
	/**
	 * 管理 PHP 错误和异常
	 * @param mixed sender of the event
	 * @param mixed event parameter (if the event is raised by TApplication, it refers to the exception instance)
	 */
	public function handleError($sender,$param)
	{
		static $handling = false;
		$sender = null;
		restore_error_handler();
		restore_exception_handler();
		if($handling)
		{
			$this->handleRecursiveError($param);
		}
		else
		{
			$handling = true;
			if(!headers_sent())
			{
				header('Content-Type: text/html; charset=UTF-8');
			}
			if($param instanceof THttpException)
			{
				$this->handleExternalError($param->getStatusCode(),$param);
			}
			else if(DEBUG)
			{
				$this->displayException($param);
			}
			else
			{
				$this->handleExternalError(500,$param);
			}
		}
	}
	/**
	 * 显示错误函数
	 * @param integer response status code
	 * @param Exception exception instance
	 */
	protected function handleExternalError($statusCode,$exception)
	{
		if(!($exception instanceof THttpException))
		{
			error_log($exception->__toString());
		}
		$content	= $this->getErrorTemplate($statusCode,$exception);
		$serverAdmin= isset($_SERVER['SERVER_ADMIN'])?$_SERVER['SERVER_ADMIN']:'';
		$tokens		= array(
		'%%StatusCode%%' 	=> "$statusCode",
		'%%ErrorMessage%%' 	=> htmlspecialchars($exception->getMessage()),
		'%%ServerAdmin%%' 	=> $serverAdmin,
		'%%Version%%' 		=> $_SERVER['SERVER_SOFTWARE'],
		'%%Time%%' 			=> @strftime('%Y-%m-%d %H:%M',time())
		);
		echo strtr($content,$tokens);
	}
	/**
	 * Handles error occurs during error handling (called recursive error).
	 * THttpException and errors happened when the application is in <b>Debug</b>
	 * mode will be displayed to the client user.
	 * Error is displayed without using existing template to prevent further errors.
	 * @param Exception exception instance
	 */
	protected function handleRecursiveError($exception)
	{
		error_log("Error happened while processing an existing error:\n".$exception->__toString());
		header('HTTP/1.0 500 Internal Error');
	}
	/**
	 * 显示异常信息
	 * @param Exception 异常句柄
	 */
	protected function displayException($exception)
	{
		// 模板错误处理
		if($exception instanceof TTemplateException)
		{
			$fileName	= $exception->getTemplateFile();
			$lines		= empty($fileName)?explode("\n",$exception->getTemplateSource()):@file($fileName);
			$source		= $this->getSourceCode($lines,$exception->getLineNumber());
			if($fileName==='')
			{
				$fileName='---embedded template---';
			}
			$errorLine=$exception->getLineNumber();
		}
		else
		{
			// 其他错误处理
			if(($trace = $this->getExactTrace($exception))!==null)
			{
				$fileName	= $trace['file'];
				$errorLine	= $trace['line'];
			}
			else
			{
				$fileName	= $exception->getFile();
				$errorLine	= $exception->getLine();
			}
			$source = $this->getSourceCode(@file($fileName),$errorLine);
		}
		// 组合错误到模板
		$tokens=array(
		'%%ErrorType%%'		=> get_class($exception),
		'%%ErrorMessage%%'	=> $this->_addLink(htmlspecialchars($exception->getMessage())),
		'%%SourceFile%%'	=> htmlspecialchars($fileName).' ('.$errorLine.')',
		'%%SourceCode%%'	=> $source,
		'%%StackTrace%%'	=> htmlspecialchars($exception->getTraceAsString()),
		'%%Version%%'		=> $_SERVER['SERVER_SOFTWARE'],
		'%%Time%%'			=> @strftime('%Y-%m-%d %H:%M',time())
		);
		$content = $this->getExceptionTemplate($exception);
		echo strtr($content,$tokens);
	}
	/**
	 * 获取显示异常的模板
	 * @param Exception the exception to be displayed
	 * @return string the template content
	 */
	protected function getExceptionTemplate($exception)
	{
		$exceptionFile = $this->_templatePath.'/'.self::EXCEPTION_FILE_NAME.'.html';
		
		if(($content = @file_get_contents($exceptionFile))===false)
		{
			die("不能打开这个异常模板文件 '$exceptionFile'.;");
		}
		return $content;
	}
	/**
	 * 获取错误模板
	 * @param integer status code (such as 404, 500, etc.)
	 * @param Exception the exception to be displayed
	 * @return string the template content
	 */
	protected function getErrorTemplate($statusCode,$exception)
	{
		$base = $this->getErrorTemplatePath().'/'.self::ERROR_FILE_NAME;

		if(is_file("$base$statusCode.html"))
		{
			$errorFile = "$base$statusCode.html";
		}
		else if(is_file("$base$statusCode.html"))
		{
			$errorFile = "$base$statusCode.html";
		}
		else if(is_file("$base.html"))
		{
			$errorFile = "$base.html";
		}
		else
		{
			$errorFile = "$base.html";
		}
		if(($content = @file_get_contents($errorFile))===false)
		{
			die("Unable to open error template file '$errorFile'. LINE :".__LINE__);
		}
		return $content;
	}
	/**
	 * 获得错误跟踪
	 * @param 异常对象 $exception
	 * @return array
	 */
	private function getExactTrace($exception)
	{
		$trace		= $exception->getTrace();
		$result		= null;
		if($exception instanceof TPhpErrorException)
		{
			if (isset($trace[0]['file']))
			{
				$result = $trace[0];
			}
			else
			{
				$result = $trace[1];
			}
		}
		else if($exception instanceof TInvalidOperationException)
		{
			if(($result = $this->getPropertyAccessTrace($trace,'__get'))===null)
			{
				$result = $this->getPropertyAccessTrace($trace,'__set');
			}
		}
		else if ($exception instanceof TDbException)
		{
			$result = $trace[1];
		}
		if($result!==null && strpos($result['file'],': eval()\'d code')!==false)
		{
			return null;
		}
		return $result;
	}
	/**
	 * 获得权限跟踪
	 * @param 异常跟踪数据 $trace
	 * @param 对象的方法 $pattern
	 * @return unknown
	 */
	private function getPropertyAccessTrace($trace,$pattern)
	{
		$result = null;
		foreach($trace as $t)
		{
			if(isset($t['function']) && $t['function']===$pattern)
			{
				$result=$t;
			}
			else
			{
				break;
			}
		}
		return $result;
	}
	/**
	 * 获得错误文件的源码
	 * @param 异常文件的行数 $lines
	 * @param 错误所在行数 $errorLine
	 * @return string
	 */
	private function getSourceCode($lines,$errorLine)
	{
		$beginLine	= $errorLine-self::SOURCE_LINES>=0?$errorLine-self::SOURCE_LINES:0;
		$endLine	= $errorLine+self::SOURCE_LINES<=count($lines)?$errorLine+self::SOURCE_LINES:count($lines);
		$source		= '';
		for($i = $beginLine;$i<$endLine;++$i)
		{
			if($i===$errorLine-1)
			{
				$line	 = htmlspecialchars(sprintf("%04d: %s",$i+1,str_replace("\t",'    ',$lines[$i])));
				$source .= "<div class=\"error\">".$line."</div>";
			}
			else
			{
				$source	.= htmlspecialchars(sprintf("%04d: %s",$i+1,str_replace("\t",'    ',$lines[$i])));
			}
		}
		return $source;
	}
	/**
	 * 增加链接
	 * @param 提示内容 $message
	 * @return string
	 */
	private function _addLink($message)
	{
		return preg_replace('/\b(T[A-Z]\w+)\b/',"<a href=\"http://www.treacle.cn/\${1}\" target=\"_blank\">\${1}</a>",$message);
	}
}
// +--------------------------------------------------------------------------
// |   Last Modified By BoPo
// |   Date: Fri Nov 16 23:38:03 CST 2007
// +--------------------------------------------------------------------------
?>